﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using WindowsGameInput;



namespace WindowsGame
{
	[Serializable]
	public struct GameSettings
	{
		public int resWidth;
		public int resHeigth;
		public uint volume;
		public bool fullscreen;
		public bool vSync;
	}

	[Serializable]
	public struct BestScore
	{
		public int score;
	}

	public interface IGameOptionsService
	{
		int Difficult { get; }
		bool Loaded { get; }
		GameSettings Settings { get; }
		event Action OptionsChanges;
	}


	public class MenuHandler : Microsoft.Xna.Framework.DrawableGameComponent, IGameOptionsService
	{
		IGameResourcesService gameResourcesService;
		IInputEventService inputEventService;
		SpriteBatch spriteBatch;

		Menu mainMenu;
		Menu newGameMenu;
		Menu inGameMenu;
		Menu currentMenu;

		public GameSettings Settings { get { return gameSettings; } }
		public int Difficult { get { return difficult; } }
		public bool Loaded { get { return loaded; } }

		DisplayMode[] supportedDisplayMode;
		uint currentDisplayModeIndex = 0;

		int difficult = 0;
		bool loaded = false;
		bool inGame = false;
		bool gameStartsAtLeastOnce = false;
		GameSettings gameSettings;
		GameSettings tmpGameSettings;
		BestScore bestScore;
		bool optionsChanges = false;
		public event Action OptionsChanges;



		public MenuHandler(Game game)
			: base(game)
		{
			Game.Services.AddService(typeof(IGameOptionsService), this);
		}



		public override void Initialize()
		{
			gameResourcesService = Game.Services.GetService(typeof(IGameResourcesService)) as IGameResourcesService;
			inputEventService = Game.Services.GetService(typeof(IInputEventService)) as IInputEventService;
			inputEventService.Quit += OnEnterInGameMenu;

			base.Initialize();
		}



		public void OnEnterInGameMenu()
		{
			if(inGame)
			{
				foreach(GameLogic logic in Game.Components.OfType<GameLogic>())
				{
					logic.Visible = false;
					logic.Enabled = false;
				}
				foreach(Rendering rendering in Game.Components.OfType<Rendering>())
				{
					rendering.Visible = false;
					rendering.Enabled = false;
				}

				currentMenu = inGameMenu;
				inGame = false;
			}
		}


		public void OnNewGame()
		{
			currentMenu = newGameMenu;
		}



		public void OnMainMenu()
		{
			currentMenu = mainMenu;
		}



		public void OnOptions()
		{
			currentMenu = CreateOptionMenu();
			tmpGameSettings = gameSettings;
		}



		public void OnStartNewGame()
		{
			loaded = false;
			OnGameStart();
		}



		public void OnGameStart()
		{
			gameStartsAtLeastOnce = true;

			while(Game.Components.OfType<GameLogic>().Count() > 0)
				Game.Components.OfType<GameLogic>().ElementAt<GameLogic>(0).Dispose();

			while(Game.Components.OfType<Rendering>().Count() > 0)
				Game.Components.OfType<Rendering>().ElementAt<Rendering>(0).Dispose();

			Game.Components.Add(new GameLogic(Game));

			Rendering rendering = new Rendering(Game);
			rendering.DrawOrder = 0;
			Game.Components.Add(rendering);
			this.DrawOrder = 1;

			OnGameResume();
		}



		public void OnGameResume()
		{
			inGame = true;

			foreach(GameComponent gameComponent in Game.Components.OfType<GameComponent>())
				gameComponent.Enabled = true;

			foreach(DrawableGameComponent dGameComponent in Game.Components.OfType<DrawableGameComponent>())
				dGameComponent.Visible = true;

			currentMenu = CreateInGameInterface();
		}

		

		public void OnSaveOptions()
		{
			if(optionsChanges)
			{
				IsolatedStorageFile userStorage = IsolatedStorageFile.GetUserStoreForDomain();
				IsolatedStorageFileStream fileStream = userStorage.OpenFile("settings.set", System.IO.FileMode.Create);

				var binFormatter = new BinaryFormatter();
				binFormatter.Serialize(fileStream, tmpGameSettings);
				fileStream.Close();

				optionsChanges = false;

				SoundsHandler.SetVolume(tmpGameSettings.volume);

				if(!gameStartsAtLeastOnce)
				{
					gameSettings = tmpGameSettings;
					if(OptionsChanges != null)
						OptionsChanges();

					mainMenu = CreateMainMenu();
					newGameMenu = CreateNewGameMenu();
					inGameMenu = CreateInGameMenu();
				}
				else
				{
					gameSettings.volume = tmpGameSettings.volume;
					currentMenu = CreateStringMenu("YOU MUST RESTART THE GAME FOR\nTHESE CHANGES TO TAKE EFFECT",
						gameResourcesService.Menu.optionsMenuTitle, mainMenu);
					return;
				}
			}
			currentMenu = mainMenu;
		}



		public void OnLoadOptions()
		{
			IsolatedStorageFile userStorage = IsolatedStorageFile.GetUserStoreForDomain();

			if(userStorage.FileExists("settings.set"))
			{
				IsolatedStorageFileStream fileStream = userStorage.OpenFile("settings.set", System.IO.FileMode.Open);

				var binFormatter = new BinaryFormatter();
				gameSettings = (GameSettings)binFormatter.Deserialize(fileStream);

				fileStream.Close();
			}
		}



		public void OnSaveBestScore()
		{
			IsolatedStorageFile userStorage = IsolatedStorageFile.GetUserStoreForDomain();
			IsolatedStorageFileStream fileStream = userStorage.OpenFile("score.sco", System.IO.FileMode.Create);

			var binFormatter = new BinaryFormatter();
			binFormatter.Serialize(fileStream, bestScore);

			fileStream.Close();
		}



		public void OnLoadBestScore()
		{
			IsolatedStorageFile userStorage = IsolatedStorageFile.GetUserStoreForDomain();

			if(userStorage.FileExists("score.scr"))
			{
				IsolatedStorageFileStream fileStream = userStorage.OpenFile("score.sco", System.IO.FileMode.Open);

				var binFormatter = new BinaryFormatter();
				bestScore = (BestScore)binFormatter.Deserialize(fileStream);

				fileStream.Close();
			}
			else
				bestScore.score = -9999;
		}



		public void OnSaveGame()
		{
			IsolatedStorageFile userStorage = IsolatedStorageFile.GetUserStoreForDomain();
			IsolatedStorageFileStream fileStream = userStorage.OpenFile("GameSave.sav", System.IO.FileMode.Create);

			var binFormatter = new BinaryFormatter();
			binFormatter.Serialize(fileStream, GameState.currentState);

			fileStream.Close();

			currentMenu = CreateStringMenu("      THE GAME HAS BEEN SAVED\n      SUCCESSFULLY!!",
						gameResourcesService.Menu.inGameMenuTitle, inGameMenu);
		}



		public void OnLoadGame()
		{
			IsolatedStorageFile userStorage = IsolatedStorageFile.GetUserStoreForDomain();

			if(userStorage.FileExists("GameSave.sav"))
			{
				IsolatedStorageFileStream fileStream = userStorage.OpenFile("GameSave.sav", System.IO.FileMode.Open);

				var binFormatter = new BinaryFormatter();
				GameState.currentState = (GameState.State)binFormatter.Deserialize(fileStream);

				fileStream.Close();
				loaded = true;
				OnGameStart();
			}
		}



		public void OnCredits()
		{
			currentMenu = CreateStringMenu("CREATED BY: FEDERICO COLOMBO",
						gameResourcesService.Menu.creditsMenuTitle, mainMenu);
		}



		public void OnQuit()
		{
			Game.Exit();
		}



		public void OnDifficultChange(Selector sender, bool plus)
		{
			if(difficult == 0 && plus)
			{
				++difficult;
				sender.ChangeSelected(gameResourcesService.Menu.hardCaption);
				optionsChanges = true;
			}
			else if(difficult == 1 && !plus)
			{
				--difficult;
				sender.ChangeSelected(gameResourcesService.Menu.easyCaption);
				optionsChanges = true;
			}
		}



		public void OnResolutionChange(Selector sender, bool plus)
		{
			bool isModeChanged = false;

			if(plus)
			{
				if(currentDisplayModeIndex < supportedDisplayMode.Count()-1)
				{
					++currentDisplayModeIndex;
					isModeChanged = true;
				}
			}
			else if(currentDisplayModeIndex > 0)
			{
				--currentDisplayModeIndex;
				isModeChanged = true;
			}

			if(isModeChanged)
			{
				int width = supportedDisplayMode[currentDisplayModeIndex].Width;
				int height = supportedDisplayMode[currentDisplayModeIndex].Height;
				tmpGameSettings.resWidth = width;
				tmpGameSettings.resHeigth = height;
				sender.ChangeSelected(width + "x" + height);
				optionsChanges = true;
			}
		}



		public void OnVolumeChange(Selector sender, bool plus)
		{
			if(plus)
			{
				if(tmpGameSettings.volume < 10)
				{
					++tmpGameSettings.volume;
					sender.ChangeSelected("     "+tmpGameSettings.volume.ToString());
					optionsChanges = true;
				}
			}
			else if(tmpGameSettings.volume > 0)
			{
				--tmpGameSettings.volume;
				sender.ChangeSelected("     "+tmpGameSettings.volume.ToString());
				optionsChanges = true;
			}
		}



		public void OnVSyncChange(Selector sender, bool plus)
		{
			if(tmpGameSettings.vSync && !plus)
			{
				tmpGameSettings.vSync = !tmpGameSettings.vSync;
				sender.ChangeSelected(gameResourcesService.Menu.offCaption);
				optionsChanges = true;
			}
			else if(!tmpGameSettings.vSync && plus)
			{
				tmpGameSettings.vSync = !tmpGameSettings.vSync;
				sender.ChangeSelected(gameResourcesService.Menu.onCaption);
				optionsChanges = true;
			}
		}



		public void OnFullscreenChange(Selector sender, bool plus)
		{
			if(tmpGameSettings.fullscreen && !plus)
			{
				tmpGameSettings.fullscreen = !tmpGameSettings.fullscreen;
				sender.ChangeSelected(gameResourcesService.Menu.offCaption);
				optionsChanges = true;
			}
			else if(!tmpGameSettings.fullscreen && plus)
			{
				tmpGameSettings.fullscreen = !tmpGameSettings.fullscreen;
				sender.ChangeSelected(gameResourcesService.Menu.onCaption);
				optionsChanges = true;
			}
		}



		protected override void LoadContent()
        {
			gameSettings = new GameSettings();
			spriteBatch = new SpriteBatch(GraphicsDevice);

			OnLoadBestScore();

			supportedDisplayMode = Game.GraphicsDevice.Adapter.SupportedDisplayModes.Where(
				(DisplayMode dM) => dM.Format == SurfaceFormat.Color).OrderBy((DisplayMode dM) => dM.Width).ToArray();

			gameSettings.resWidth = Game.GraphicsDevice.Adapter.CurrentDisplayMode.Width;
			gameSettings.resHeigth = Game.GraphicsDevice.Adapter.CurrentDisplayMode.Height;
			gameSettings.volume = SoundsHandler.GetVolume();
			gameSettings.fullscreen = false;
			gameSettings.vSync = true;

			OnLoadOptions();

			for(uint i = 0; i < supportedDisplayMode.Count(); ++i)
				if(supportedDisplayMode[i].Width == gameSettings.resWidth && supportedDisplayMode[i].Height == gameSettings.resHeigth)
					currentDisplayModeIndex = i;

			SoundsHandler.SetVolume(gameSettings.volume);
			if(OptionsChanges != null)
				OptionsChanges();

			mainMenu = CreateMainMenu();
			newGameMenu = CreateNewGameMenu();
			inGameMenu = CreateInGameMenu();

			currentMenu = mainMenu;

            base.LoadContent();
        }



		private Menu CreateMainMenu()
		{
			float width = gameSettings.resWidth;
			float height = gameSettings.resHeigth;
			Menu tmpMenu = new Menu(gameResourcesService.Menu.menuBackground);
			tmpMenu.AddText(new Vector2(0.518f * width, 0.065f * height), gameResourcesService.Menu.mainMenuTitle);
			tmpMenu.AddButton(new Vector2(0.635f * width, 0.247f * height), gameResourcesService.Menu.newGame,
									gameResourcesService.Menu.newGameOn).ButtonClick += OnNewGame;
			tmpMenu.AddButton(new Vector2(0.684f * width, 0.339f * height), gameResourcesService.Menu.loadGame,
									gameResourcesService.Menu.loadGameOn).ButtonClick += OnLoadGame;
			tmpMenu.AddButton(new Vector2(0.625f * width, 0.430f * height), gameResourcesService.Menu.options,
									gameResourcesService.Menu.optionsOn).ButtonClick += OnOptions;
			tmpMenu.AddButton(new Vector2(0.693f * width, 0.521f * height), gameResourcesService.Menu.credits,
									gameResourcesService.Menu.creditsOn).ButtonClick += OnCredits;
			tmpMenu.AddButton(new Vector2(0.664f * width, 0.612f * height), gameResourcesService.Menu.exitGame,
									gameResourcesService.Menu.exitGameOn).ButtonClick += OnQuit;
			tmpMenu.AddText(new Vector2(0.72f * width, 0.84f * height), "BEST SCORE:",
				new Color(80, 25, 25), gameResourcesService.Menu.spriteFont);
			tmpMenu.AddText(new Vector2(0.78f * width, 0.88f * height), bestScore.score.ToString(),
				new Color(80, 25, 25), gameResourcesService.Menu.spriteFont);
			tmpMenu.TextUpdate += onMainMenuTextUpdate;
			return tmpMenu;
		}



		private Menu CreateInGameMenu()
		{
			float width = gameSettings.resWidth;
			float height = gameSettings.resHeigth;
			Menu tmpMenu = new Menu(gameResourcesService.Menu.menuBackground);
			tmpMenu.AddText(new Vector2(0.525f * width, 0.075f * height), gameResourcesService.Menu.inGameMenuTitle);
			tmpMenu.AddButton(new Vector2(0.635f * width, 0.247f * height), gameResourcesService.Menu.resumeGame,
									gameResourcesService.Menu.resumeGameOn).ButtonClick += OnGameResume;
			tmpMenu.AddButton(new Vector2(0.684f * width, 0.339f * height), gameResourcesService.Menu.saveGame,
									gameResourcesService.Menu.saveGameOn).ButtonClick += OnSaveGame;
			tmpMenu.AddButton(new Vector2(0.625f * width, 0.430f * height), gameResourcesService.Menu.loadGame,
									gameResourcesService.Menu.loadGameOn).ButtonClick += OnLoadGame;
			tmpMenu.AddButton(new Vector2(0.693f * width, 0.521f * height), gameResourcesService.Menu.mainMenu,
									gameResourcesService.Menu.mainMenuOn).ButtonClick += () => { currentMenu =
										CreateStringMenu("      ARE YOU SURE YOU WANT TO\n      LEAVE THIS GAME?",
										gameResourcesService.Menu.inGameMenuTitle, mainMenu, inGameMenu); };
			return tmpMenu;
		}



		private Menu CreateNewGameMenu()
		{
			Texture2D difficultyCaption = difficult == 0 ? gameResourcesService.Menu.easyCaption : gameResourcesService.Menu.hardCaption;

			float width = gameSettings.resWidth;
			float height = gameSettings.resHeigth;
			Menu tmpMenu = new Menu(gameResourcesService.Menu.menuBackground);
			tmpMenu.AddText(new Vector2(0.524f * width, 0.076f * height), gameResourcesService.Menu.newGameMenuTitle);
			tmpMenu.AddSelector(new Vector2(0.508f * width, 0.26f * height), gameResourcesService.Menu.plus, gameResourcesService.Menu.plusOn,
					gameResourcesService.Menu.minus, gameResourcesService.Menu.minusOn, gameResourcesService.Menu.difficulty,
					difficultyCaption, (int)width).SelectionChange += OnDifficultChange;

			//tmpMenu.AddText(new Vector2(0.51f * width, 0.38f * height), "KEY COMMANDS:\n\nMOVE: ARROW KEYS\nFIRE:   CTRL SX",
			//    new Color(80, 25, 25), gameResourcesService.Menu.spriteFont);
			tmpMenu.AddText(new Vector2(0.67f * width, 0.61f * height), "KEY COMMANDS:\n\nMOVE: ARROW KEYS\nFIRE:   CTRL SX",
				new Color(80, 25, 25), gameResourcesService.Menu.spriteFont);

			tmpMenu.AddButton(new Vector2(0.679f * width, 0.8854f * height), gameResourcesService.Menu.back,
									gameResourcesService.Menu.backOn).ButtonClick += OnMainMenu;
			tmpMenu.AddButton(new Vector2(0.85f * width, 0.8854f * height), gameResourcesService.Menu.done,
									gameResourcesService.Menu.doneOn).ButtonClick += OnStartNewGame;
			return tmpMenu;
		}



		private Menu CreateOptionMenu()
		{
			Texture2D fullscreenCaption = gameSettings.fullscreen ? gameResourcesService.Menu.onCaption : gameResourcesService.Menu.offCaption;
			Texture2D vSyncCaption = gameSettings.vSync ? gameResourcesService.Menu.onCaption : gameResourcesService.Menu.offCaption;

			float width = gameSettings.resWidth;
			float height = gameSettings.resHeigth;
			Menu tmpMenu = new Menu(gameResourcesService.Menu.menuBackground);
			tmpMenu.AddText(new Vector2(0.525f * width, 0.075f * height), gameResourcesService.Menu.optionsMenuTitle);
			tmpMenu.AddSelector(new Vector2(0.508f * width, 0.260f * height), gameResourcesService.Menu.plus, gameResourcesService.Menu.plusOn,
						gameResourcesService.Menu.minus, gameResourcesService.Menu.minusOn, gameResourcesService.Menu.resolution,
						width + "x" + height, new Color(80, 25, 25), gameResourcesService.Menu.spriteFont,
						(int)width).SelectionChange += OnResolutionChange;
			tmpMenu.AddSelector(new Vector2(0.508f * width, 0.344f * height), gameResourcesService.Menu.plus, gameResourcesService.Menu.plusOn,
						gameResourcesService.Menu.minus, gameResourcesService.Menu.minusOn, gameResourcesService.Menu.vSync,
						vSyncCaption, (int)width).SelectionChange += OnVSyncChange;
			tmpMenu.AddSelector(new Vector2(0.508f * width, 0.42f * height), gameResourcesService.Menu.plus, gameResourcesService.Menu.plusOn,
						gameResourcesService.Menu.minus, gameResourcesService.Menu.minusOn, gameResourcesService.Menu.fullscreen,
						fullscreenCaption, (int)width).SelectionChange += OnFullscreenChange;
			tmpMenu.AddSelector(new Vector2(0.508f * width, 0.496f * height), gameResourcesService.Menu.plus, gameResourcesService.Menu.plusOn,
						gameResourcesService.Menu.minus, gameResourcesService.Menu.minusOn, gameResourcesService.Menu.volume,
						"     "+SoundsHandler.GetVolume().ToString(), new Color(80, 25, 25), gameResourcesService.Menu.spriteFont,
						(int)width).SelectionChange += OnVolumeChange;
			tmpMenu.AddButton(new Vector2(0.679f * width, 0.8854f * height), gameResourcesService.Menu.back,
									gameResourcesService.Menu.backOn).ButtonClick += OnMainMenu;
			tmpMenu.AddButton(new Vector2(0.85f * width, 0.8854f * height), gameResourcesService.Menu.done,
									gameResourcesService.Menu.doneOn).ButtonClick += OnSaveOptions;
			return tmpMenu;
		}



		private Menu CreateInGameInterface()
		{
			float width = gameSettings.resWidth;
			float height = gameSettings.resHeigth;
			Menu tmpMenu = new Menu();
			tmpMenu.AddText(new Vector2(0.75f * width, 0.015f * height), "Health: ",
				new Color(255, 250, 210), gameResourcesService.Menu.spriteFont);
			tmpMenu.AddText(new Vector2(0.85f * width, 0.015f * height), GameState.currentState.player.health.Value.ToString(),
				new Color(255, 250, 210), gameResourcesService.Menu.spriteFont);
			tmpMenu.AddText(new Vector2(0.75f * width, 0.045f * height), "Score:  ",
				new Color(255, 250, 210), gameResourcesService.Menu.spriteFont);
			tmpMenu.AddText(new Vector2(0.85f * width, 0.045f * height), GameState.currentState.score.Value.ToString(),
				new Color(255, 250, 210), gameResourcesService.Menu.spriteFont);
			tmpMenu.TextUpdate += onInterfaceTextUpdate;
			return tmpMenu;
		}


		private Menu CreateStringMenu(string text, Texture2D title, Menu doneDest, Menu backDest = null, float titleXOffeset = 0)
		{
			float width = gameSettings.resWidth;
			float height = gameSettings.resHeigth;
			Menu tmpMenu = new Menu(gameResourcesService.Menu.menuBackground);
			tmpMenu.AddText(new Vector2((titleXOffeset+0.525f) * width, 0.075f * height), title);
			tmpMenu.AddText(new Vector2(0.44f * width, 0.25f * height), text, new Color(80, 25, 25),
				gameResourcesService.Menu.spriteFont);
			tmpMenu.AddButton(new Vector2(0.85f * width, 0.8854f * height), gameResourcesService.Menu.done,
				gameResourcesService.Menu.doneOn).ButtonClick += () => { currentMenu = doneDest; };
			if(backDest != null)
				tmpMenu.AddButton(new Vector2(0.679f * width, 0.8854f * height), gameResourcesService.Menu.back,
					gameResourcesService.Menu.backOn).ButtonClick += () => { currentMenu = backDest; };

			return tmpMenu;
		}



		protected override void Dispose(bool disposing)
		{
			// Remove services here
			Game.Services.RemoveService(typeof(IGameOptionsService));
			base.Dispose(disposing);
		}



		protected void onInterfaceTextUpdate(Text sender, uint id)
		{
			if(id == 1)
				sender.ChangeCaption(GameState.currentState.player.health.Value.ToString());
			else if(id == 3)
				sender.ChangeCaption(GameState.currentState.score.Value.ToString());
		}


		protected void onMainMenuTextUpdate(Text sender, uint id)
		{
			if(id == 2)
				sender.ChangeCaption(bestScore.score == -9999 ? "N/A" : bestScore.score.ToString());
		}



		public override void Update(GameTime gameTime)
		{
			if(inGame)
			{
				if(GameState.currentState.gameLost.Value)
				{
					SoundsHandler.PlaySound(SoundsHandler.SoundType.DEFEAT);
					OnEnterInGameMenu();
					currentMenu = CreateStringMenu("GAME OVER!!\n\nSCORE: " + GameState.currentState.score.Value.ToString(), 
						gameResourcesService.Menu.endingGameTitle, mainMenu, null, -0.09f);
					if(GameState.currentState.score.Value > bestScore.score)
					{
						bestScore.score = GameState.currentState.score.Value;
						OnSaveBestScore();
					}
				}
				else if(GameState.currentState.gameWon.Value)
				{
					SoundsHandler.PlaySound(SoundsHandler.SoundType.WIN);
					OnEnterInGameMenu();
					currentMenu = CreateStringMenu("YOU WIN!!\n\nSCORE: " + GameState.currentState.score.Value.ToString(),
						gameResourcesService.Menu.endingGameTitle, mainMenu, null, -0.09f);
					if(GameState.currentState.score.Value > bestScore.score)
					{
						bestScore.score = GameState.currentState.score.Value;
						OnSaveBestScore();
					}
				}
			}
			currentMenu.Update(gameTime);
			base.Update(gameTime);
		}



		public override void Draw(GameTime gameTime)
		{
			float scale = gameSettings.resWidth / 1280.0f;

			spriteBatch.Begin();
			currentMenu.Draw(spriteBatch, gameTime, scale);
			spriteBatch.End();

			base.Draw(gameTime);
		}
	}
}
